﻿using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using GameEngine.Model;
using GameEngine.Model.Data;
using System.Collections.Generic;
using GameEngine.Libs;
using System.Diagnostics;
using GameEngine.Clients.Silverlight;
using SilverlightResources;

namespace MyUniverse
{
    public class VegetalVO : ThinkingThingVO<int>
    {
        public int Energy { get; set; }

        public bool Dead { get; set; }

        public VegetalVO(IThingVOId<int> id, IThingTypeVOId typeId) :
            base(id, typeId)
        {
        }
    }

    public class AnimalVO : VegetalVO
    {
        public Guid Food { get; set; }

        public AnimalVO(IThingVOId<int> id, IThingTypeVOId typeId) :
            base(id, typeId)
        {

        }
    }

    public class VegetalTypeVO : ThinkingThingTypeVO<int>
    {
        public VegetalTypeVO(IThingTypeVOId id) : base(id) { }
    }

    public class AnimalTypeVO : VegetalTypeVO
    {
        public AnimalTypeVO(IThingTypeVOId id) : base(id) { }
    }

    public enum AnimalAction { Nothing = 0, Eat = 2, Sex = 4, GoToNearestFood = 6, GoToNearestPartner = 8, Find = 10 }
    public enum TouchTypes { Nothing = 0, Food = 3, SexualPartner = 6, Both = 9 }
    public enum SeeTypes { Nothing = 0, Food = 3, SexualPartner =6, Both = 9 }

    public class VegetalDataProxy : ThinkingThingDataProxy<int>
    {
        public virtual bool Dead
        {
            get { return VO.Dead; }
            set
            {
                VO.Dead = value;
                UpdateState();
                SilverlightApplicationFacadeHelper.RemoveThingFromWorld(this.Id,this.WorldId);
            }
        }

        public virtual int Energy
        {
            get { return VO.Energy; }
            set
            {
                VO.Energy = value;
                UpdateLabel();
                if (Energy <= 0)
                {
                    Debug.WriteLine("Dead");
                    Dead = true;
                }
            }
        }

        public new VegetalVO VO
        {
            get { return this.Data as VegetalVO; }
        }

        public VegetalDataProxy(VegetalVO item)
            : base(item)
        {
        }

        public override void UpdateState()
        {
            base.UpdateState();
            if (Dead)
                State = MyFacade.deadState;
            UpdateLabel();    
        }

        void UpdateLabel() {
            SampleSilverlightViewFacade.Helper.GetThingView(Id).Tooltip = NAME + " " + Energy;
        }
    }

    public class AnimalDataProxy : VegetalDataProxy
    {
        public override bool Dead
        {
            set
            {
                VO.Dead = value;
                UpdateState();
            }
        }

        public AnimalAction Action
        {
            get;
            set;
        }

        public bool Hungry
        {
            get { return Energy < 40; }
        }

        public new AnimalVO VO
        {
            get { return this.Data as AnimalVO; }
        }

        public Guid Food
        {
            get { return VO.Food; }
        }

        //class com mais items
        public AnimalAction MemoryLastAction
        {
            get;
            set;
        }

        public static float[] buildInput(bool nothing, bool hungry, TouchTypes touch, SeeTypes see) {
                return new float[] { 
                    nothing?1:0,
                    hungry?1:0,
                    touch == TouchTypes.Nothing ?1:0,
                    touch == TouchTypes.Food?1:0,
                    touch == TouchTypes.SexualPartner?1:0,
                    touch == TouchTypes.Both?1:0,
                    see == SeeTypes.Nothing?1:0,
                    see == SeeTypes.Food?1:0,
                    see == SeeTypes.SexualPartner?1:0,
                    see == SeeTypes.Both?1:0,};
        } 

        public static float[] buildOutput(AnimalAction action){
            switch (action) { 
                case AnimalAction.Nothing:
                    return new float[] { 1, 0, 0, 0, 0, 0 };
                case AnimalAction.Eat:
                    return new float[] { 0, 1, 0, 0, 0, 0 };
                case AnimalAction.Sex:
                    return new float[] { 0, 0, 1, 0, 0, 0 };
                case AnimalAction.GoToNearestFood:
                    return new float[] { 0, 0, 0, 1, 0, 0 };
                case AnimalAction.GoToNearestPartner:
                    return new float[] { 0, 0, 0, 0, 1, 0 };
                case AnimalAction.Find:
                    return new float[] { 0, 0, 0, 0, 0, 1 };
            }
            return new float[] { 0, 0, 0, 0, 0, 0 };
        }

        static NeuralNetwork sampleBrain; 

        public AnimalDataProxy(AnimalVO item)
            : base(item)
        {
            if (sampleBrain == null) {
                sampleBrain = new NeuralNetwork(10, new int[] { 6 });

                LearningAlgorithm learning = new GeneticLearningAlgorithm(sampleBrain);
                //LearningAlgorithm learning = new BackPropagationLearningAlgorithm(Brain);
                //            learning.RandomizePopulation();
                learning.Learn(
                    new float[][]{
                           //Herdado,Fome,   Touch,              Ver
                    
                    // Not hungry
                    
                    buildInput(false, false, TouchTypes.Nothing, SeeTypes.Nothing),
                    buildInput(false, false, TouchTypes.Nothing, SeeTypes.SexualPartner),
                    buildInput(false, false, TouchTypes.Nothing, SeeTypes.Food),
                    buildInput(false, false, TouchTypes.Nothing, SeeTypes.Both),

                    buildInput(false, false, TouchTypes.Food, SeeTypes.Nothing),
                    buildInput(false, false, TouchTypes.Food, SeeTypes.SexualPartner),
                    buildInput(false, false, TouchTypes.Food, SeeTypes.Food),
                    buildInput(false, false, TouchTypes.Food, SeeTypes.Both),

                    buildInput(false, false, TouchTypes.SexualPartner, SeeTypes.Nothing),
                    buildInput(false, false, TouchTypes.SexualPartner, SeeTypes.SexualPartner),
                    buildInput(false, false, TouchTypes.SexualPartner, SeeTypes.Food),
                    buildInput(false, false, TouchTypes.SexualPartner, SeeTypes.Both),

                    buildInput(false, false, TouchTypes.Both, SeeTypes.Nothing),
                    buildInput(false, false, TouchTypes.Both, SeeTypes.SexualPartner),
                    buildInput(false, false, TouchTypes.Both, SeeTypes.Food),
                    buildInput(false, false, TouchTypes.Both, SeeTypes.Both),
                    
                    //Hungry
                    
                    buildInput(false, true, TouchTypes.Nothing, SeeTypes.Nothing),
                    buildInput(false, true, TouchTypes.Nothing, SeeTypes.SexualPartner),
                    buildInput(false, true, TouchTypes.Nothing, SeeTypes.Food),
                    buildInput(false, true, TouchTypes.Nothing, SeeTypes.Both),

                    buildInput(false, true, TouchTypes.Food,    SeeTypes.Nothing),
                    buildInput(false, true, TouchTypes.Food,    SeeTypes.SexualPartner),
                    buildInput(false, true, TouchTypes.Food,    SeeTypes.Food),
                    buildInput(false, true, TouchTypes.Food,    SeeTypes.Both),

                    buildInput(false, true, TouchTypes.SexualPartner, SeeTypes.Nothing),
                    buildInput(false, true, TouchTypes.SexualPartner, SeeTypes.SexualPartner),
                    buildInput(false, true, TouchTypes.SexualPartner, SeeTypes.Food),
                    buildInput(false, true, TouchTypes.SexualPartner, SeeTypes.Both),

                    buildInput(false, true, TouchTypes.Both, SeeTypes.Nothing),
                    buildInput(false, true, TouchTypes.Both, SeeTypes.SexualPartner),
                    buildInput(false, true, TouchTypes.Both, SeeTypes.Food),
                    buildInput(false, true, TouchTypes.Both, SeeTypes.Both),
                },
                    new float[][]{
                              //Action(0-nada, 1-Eat,2-Sex,3-GoToNearestFood,4-GoToNearestPartner,5-Find)
                              //        TurnRight TurnLeft MoveForward -> é dificil ser o cérebro a dicidir
                    buildOutput(AnimalAction.Find),
                    buildOutput(AnimalAction.GoToNearestPartner),
                    buildOutput(AnimalAction.Find),
                    buildOutput(AnimalAction.GoToNearestPartner),

                    buildOutput(AnimalAction.Find),
                    buildOutput(AnimalAction.GoToNearestPartner),
                    buildOutput(AnimalAction.Find),
                    buildOutput(AnimalAction.GoToNearestPartner),

                    buildOutput(AnimalAction.Sex),
                    buildOutput(AnimalAction.Sex),
                    buildOutput(AnimalAction.Sex),
                    buildOutput(AnimalAction.Sex),

                    buildOutput(AnimalAction.Sex),
                    buildOutput(AnimalAction.Sex),
                    buildOutput(AnimalAction.Sex),
                    buildOutput(AnimalAction.Sex),
                    //Hungry
                    buildOutput(AnimalAction.Find),
                    buildOutput(AnimalAction.Find),
                    buildOutput(AnimalAction.GoToNearestFood),
                    buildOutput(AnimalAction.GoToNearestFood),

                    buildOutput(AnimalAction.Eat),
                    buildOutput(AnimalAction.Eat),
                    buildOutput(AnimalAction.Eat),
                    buildOutput(AnimalAction.Eat),

                    buildOutput(AnimalAction.Find),
                    buildOutput(AnimalAction.Find),
                    buildOutput(AnimalAction.GoToNearestFood),
                    buildOutput(AnimalAction.GoToNearestFood),

                    buildOutput(AnimalAction.Eat),
                    buildOutput(AnimalAction.Eat),
                    buildOutput(AnimalAction.Eat),
                    buildOutput(AnimalAction.Eat),
                    //run away from, goto, eat, 
                });
                //learning.Learn(
                //    new float[][]{
                //        new float[]{0,1,    0, SeeTypes.Food},
                //    },
                //    new float[][]{
                //        new float[]{0,0,0,1,0,0},//PresaAction.GoToNearestFood},
                //    });
                //learning.Learn(
                //    new float[][]{
                //        new float[]{0,0,    0, SeeTypes.Food},
                //    },
                //    new float[][]{
                //        new float[]{0,0,0,0,0,1},//PresaAction.Find
                //    });
                Debug.WriteLine("Brain iterations: " + learning.Iteration + " of " + learning.MaxIteration);
            }
            Brain = sampleBrain;
            
        }

        override public void UpdateState()
        {
            if (Dead)
            {
                State = MyFacade.deadState;
            }
            else if (Moving)
            {
                if (Direction.X > 0)
                {
                    State = SpriteMaleAndFemale.walkEastState;
                }
                else if (Direction.X < 0)
                {
                    State = SpriteMaleAndFemale.walkWestState;
                }
                else
                {
                    if (Direction.Y > 0)
                    {
                        State = SpriteMaleAndFemale.walkNorthState;
                    }
                    else if (Direction.Y < 0)
                    {
                        State = SpriteMaleAndFemale.walkSouthState;
                    }
                }
            }
            else
            {
                if (Direction.X > 0)
                {
                    //State = MySmurfsFacade.Instance.standEastState;
                    State = SpriteMaleAndFemale.standEastState;//default state
                }
                else if (Direction.X < 0)
                {
                    //State = MySmurfsFacade.Instance.standWestState;
                    State = SpriteMaleAndFemale.standWestState;
                }
                else
                {
                    if (Direction.Y > 0)
                    {
                        //State = MySmurfsFacade.Instance.standNorthState;
                        State = SpriteMaleAndFemale.standNorthState;
                    }
                    else if (Direction.Y < 0)
                    {
                        State = SpriteMaleAndFemale.standSouthState;
                    }
                }
            }
        }

        public bool IsFood(IThingDataProxy<int> thing)
        {
            bool isFood =  thing.VO.GetType().GUID.Equals(Food);
            if (isFood)
            {
                VegetalDataProxy food = thing as VegetalDataProxy;
                return !food.Dead;
            }
            return false;
        }

        public bool IsPartner(IThingDataProxy<int> thing)
        {
            bool isPartner = thing.GetType().GUID.Equals(typeof(AnimalDataProxy).GUID);
            if (isPartner)
            {
                VegetalDataProxy partner = thing as VegetalDataProxy;
                return !partner.Dead;
            }
            return false;
        }

        public void Eat(VegetalDataProxy vegetal)
        {
            //MessageBox.Show("eat");
            if (vegetal.Energy > 10)
            {
                Energy += 10;
                vegetal.Energy -= 10;
            }
            else
            {
                Energy += vegetal.Energy;
                vegetal.Energy = 0;
            }
        }
    }

    public class AnimalTypeDataProxy : ThinkingThingTypeDataProxy<int>
    {
        public const String SENSE_VISION = "vision";
        public const String SENSE_TOUCH = "touch";

        public AnimalTypeDataProxy(IThinkingThingTypeVO<int> typeVO, IViewDataProxy<int> view)
            : base(typeVO, view)
        {
            AddSense(SENSE_VISION, 5, GameEngine.Model.SensibilityRadiusEnum.forwardAndSides);
            AddSense(SENSE_TOUCH, 1, GameEngine.Model.SensibilityRadiusEnum.forwardAndBack);
        }

        public override float[] BrainInput(IWorldDataProxy<int> world, IThingDataProxy<int> item)
        {            
            AnimalDataProxy animal = item as AnimalDataProxy;
            float[] baseResults = base.BrainInput(world, item);
            //List<float> input = new List<float>();

            //input.AddRange(baseResults);
            
            bool hungry = !animal.Dead && animal.Hungry;
            TouchTypes touchSense = TouchTypes.Nothing;
            SeeTypes visionSense = SeeTypes.Nothing;

            if (!animal.Dead && animal.ThingsInReach[AnimalTypeDataProxy.SENSE_TOUCH].Count > 0)
            {
                bool foundFood = false;
                bool foundPartner = false;
                foreach (var thingPosition in animal.ThingsInReach[AnimalTypeDataProxy.SENSE_TOUCH])
                {
                    if(world.ItemsMapPosition.ContainsKey(thingPosition.Value))
                        foreach (var thingId in world.ItemsMapPosition[thingPosition.Value])
                        {
                            var thing = world.Items[thingId];
                            if (animal.IsFood(thing))
                            {
                                foundFood = true;
                            }
                            else if (animal.IsPartner(thing))
                            {
                                foundPartner = true;
                            }
                        }
                }
                if (foundFood && foundPartner)
                {
                    touchSense = TouchTypes.Both;
                }
                else { 
                    if(foundFood)
                        touchSense = TouchTypes.Food;
                    else if(foundPartner)
                        touchSense = TouchTypes.SexualPartner;
                }
            }

            if (!animal.Dead && animal.ThingsInReach[AnimalTypeDataProxy.SENSE_VISION].Count > 0)
            {
                bool foundFood = false;
                bool foundPartner = false;
                foreach (var thingPosition in animal.ThingsInReach[AnimalTypeDataProxy.SENSE_VISION])
                {
                    if(world.ItemsMapPosition.ContainsKey(thingPosition.Value))
                        foreach (var thingId in world.ItemsMapPosition[thingPosition.Value])
                        {
                            var thing = world.Items[thingId];
                            if (animal.IsFood(thing))
                            {
                                foundFood = true;
                            }
                            else if (animal.IsPartner(thing))
                            {
                                foundPartner = true;
                            }
                        }
                }
                if (foundFood && foundPartner)
                {
                    visionSense = SeeTypes.Both;
                }
                else
                {
                    if (foundFood)
                        visionSense = SeeTypes.Food;
                    else if (foundPartner)
                        visionSense = SeeTypes.SexualPartner;
                }
            }

            //input.Add(hungry);
            //input.Add(touchSense);
            //input.Add(visionSense);
            //Debug.WriteLine("Presa Brain input: " + hungry+","+touchSense+","+visionSense);
            return AnimalDataProxy.buildInput(false, hungry, touchSense, visionSense);
            //return input.ToArray();
        }

        //Aplica acções resultantes do pensamento
        public override void BrainOutput(IThingDataProxy<int> item, float[] output)
        {
            base.BrainOutput(item, output);
            AnimalDataProxy animal = item as AnimalDataProxy;
            //animal.Action = (AnimalAction)Enum.ToObject(typeof(AnimalAction), (int)Math.Round(output[0] * 10,0));
            //Debug.WriteLine("Presa Brain output: " + String.Join("; ", output));
            float biggest = 0;
            foreach (float f in output) {
                if (f > biggest) {
                    biggest = f;
                }
            }
            if (output[0] == biggest)
                animal.Action = AnimalAction.Nothing;
            if (output[1] == biggest)
                animal.Action = AnimalAction.Eat;
            if (output[2] == biggest)
                animal.Action = AnimalAction.Sex;
            if (output[3] == biggest)
                animal.Action = AnimalAction.GoToNearestFood;
            if (output[4] == biggest)
                animal.Action = AnimalAction.GoToNearestPartner;
            if (output[5] == biggest)
                animal.Action = AnimalAction.Find;
        }
    }

    public class AnimalTypeDataProxyActions
    {
        //Transforma os pensamentos em acções no mundo
        public static void Action(EngineDataProxy<int> engine, IWorldDataProxy<int> world, IThingTypeDataProxy<int> itemType, IThingDataProxy<int> item)
        {
            AnimalDataProxy thing = (AnimalDataProxy)item;
            AnimalTypeDataProxy thingType = (AnimalTypeDataProxy)itemType;

            if (!thing.Dead)
            {
                thing.Energy--;
                //need to procriate ++

                //Find(engine, world, itemType, item);
                switch (thing.Action)
                {
                    case AnimalAction.Nothing:
                        Debug.WriteLine(thing.Id.Name + " - Nothing");
                        break;
                    case AnimalAction.Eat:
                        Debug.WriteLine(thing.Id.Name + " - Eat");
                        Eat(engine, world, itemType, item);
                        break;
                    case AnimalAction.Sex:
                        Debug.WriteLine(thing.Id.Name + " - Sex");
                        //world.Items[world.ItemsMapPosition[thing.InReachMapPositionsTransformed[PresaTypeDataProxy.SENSE_TOUCH][0]]].Reproduce();
                        //MessageBox.Show("sex");
                        break;
                    case AnimalAction.GoToNearestFood:
                        Debug.WriteLine(thing.Id.Name + " - GoToNearestFood");
                        GoToNearestFood(engine, world, itemType, item);
                        break;
                    case AnimalAction.GoToNearestPartner:
                        Debug.WriteLine(thing.Id.Name + " - GoToNearestPartner");
                        GoToNearestPartner(engine, world, itemType, item);
                        break;
                    case AnimalAction.Find:
                        Debug.WriteLine(thing.Id.Name + " - Find");
                        Find(engine, world, itemType, item);
                        break;
                }
                thing.MemoryLastAction = thing.Action;
            }
        }
        
        static Random rand = new Random();
        protected static void Find(EngineDataProxy<int> engine, IWorldDataProxy<int> world, IThingTypeDataProxy<int> itemType, IThingDataProxy<int> item)
        {
            AnimalDataProxy thing = (AnimalDataProxy)item;
            AnimalTypeDataProxy thingType = (AnimalTypeDataProxy)itemType;

            if (thing.MemoryLastAction != thing.Action || thing.Destination==null)
            {
                //random position
                Point p = new Point(thing.Position.X + rand.Next(10) - 5, thing.Position.Y + rand.Next(10) - 5);
                p.X = p.X < 0 ? 0 : p.X;
                p.X = p.X > world.Map.Size.X - 1 ? world.Map.Size.X - 1 : p.X;
                p.Y = p.Y < 0 ? 0 : p.Y;
                p.Y = p.Y > world.Map.Size.Y - 1 ? world.Map.Size.Y - 1 : p.Y;
                Point? newDestination = FindNextStep(world.Map, thing.Position, p);
                if(newDestination.HasValue)
                    thing.Destination = newDestination.Value;
            }
        }

        protected static void GoToNearestPartner(EngineDataProxy<int> engine, IWorldDataProxy<int> world, IThingTypeDataProxy<int> itemType, IThingDataProxy<int> item)
        {
            AnimalDataProxy thing = (AnimalDataProxy)item;
            AnimalTypeDataProxy thingType = (AnimalTypeDataProxy)itemType;
            foreach(var pair in thing.ThingsInReach[AnimalTypeDataProxy.SENSE_VISION]){
                //TODO ver a mais perto
                var partnerId = thing.ThingsInReach[AnimalTypeDataProxy.SENSE_VISION][0].Key;
                var partner = world.Items[partnerId];
                if (partner != null && thing.IsPartner(partner))
                {
                    Point? newDestination = FindNextStep(world.Map, thing.Position, world.Map.GetNearestEmptyPosition(thing,partner));
                    if (newDestination.HasValue)
                        thing.Destination = newDestination.Value;
                    break;
                }
            }
        }

        protected static void GoToNearestFood(EngineDataProxy<int> engine, IWorldDataProxy<int> world, IThingTypeDataProxy<int> itemType, IThingDataProxy<int> item)
        {
            AnimalDataProxy thing = (AnimalDataProxy)item;
            AnimalTypeDataProxy thingType = (AnimalTypeDataProxy)itemType;

            if (thing.ThingsInReach[AnimalTypeDataProxy.SENSE_VISION].Count > 0)
            {
                //TODO ver a mais perto
                foreach(var pair in thing.ThingsInReach[AnimalTypeDataProxy.SENSE_VISION]){
                    var foodId = pair.Key;
                    var food = world.Items[foodId];
                    if (food != null && thing.IsFood(food))
                    {
                        Point? newDestination = FindNextStep(world.Map, thing.Position, world.Map.GetNearestEmptyPosition(thing,food));
                        if (newDestination.HasValue)
                            thing.Destination = newDestination.Value;
                        break;
                    }
                }
            }
        }

        protected static void Eat(EngineDataProxy<int> engine, IWorldDataProxy<int> world, IThingTypeDataProxy<int> itemType, IThingDataProxy<int> item)
        {
            AnimalDataProxy thing = (AnimalDataProxy)item;
            AnimalTypeDataProxy thingType = (AnimalTypeDataProxy)itemType;

            if (thing.ThingsInReach[AnimalTypeDataProxy.SENSE_TOUCH].Count > 0)
            {
                var foodId = thing.ThingsInReach[AnimalTypeDataProxy.SENSE_VISION][0].Key;
                var food = world.Items[foodId];
                if (food != null && thing.IsFood(food))
                {
                    VegetalDataProxy vegetal = food as VegetalDataProxy;
                    thing.Eat(vegetal);
                }
                else
                {
                    //thing.Eating = false; -> o cérebro vai parar
                }
            }
        }

        protected static Point? FindNextStep(IMapDataProxy<int> map, Point currentPosition, Point newPosition)
        {
            GameEngine.Libs.Path path = map.findPath(null,
            (int)currentPosition.X,
            (int)currentPosition.Y,
            (int)newPosition.X,
            (int)newPosition.Y);
            if (path != null && path.getLength() > 1)
            {
                GameEngine.Libs.Path.Step step = path.getStep(1);
                return new Point((double)step.getX(), (double)step.getY());
            }
            else
            {
                return null;
                //newDestination = movingThing.Position;//TODO handle caminhos impossíveis
            }
        }
    }
}
